#Importing packages
import math
import colorsys
from itertools import combinations
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from PIL import Image, ImageColor
from sklearn.cluster import KMeans, AgglomerativeClustering
def image_to_numpy(filename, colorspace):
image = Image.open(filename).convert(colorspace)
data = np.asarray(image)
y_size = data.shape[0]
x_size = data.shape[1]
data2d = np.reshape(data,(y_size * x_size, 3))
#need to cut from size of notebook XD
#scaling_param = int((x_size/800 + y_size/600) / 2) + 1
#height_list = [i for i in range(0, y_size, scaling_param)]
#width_list = [i for i in range(0, x_size, scaling_param)]
#data = data[height_list, :, :]
#data = data[:, width_list, :]
#data2d = np.reshape(data,(len(height_list)*len(width_list), 3))
return data, data2d
def reduce_image_res(filename, colorspace):
image = Image.open(filename).convert(colorspace)
data = np.asarray(image)
y_size = data.shape[0]
x_size = data.shape[1]
#reduce image to ~200x100 pixel size
scaling_param = int((x_size/200 + y_size/100) / 2)
height_list = [i for i in range(0, y_size, scaling_param)]
width_list = [i for i in range(0, x_size, scaling_param)]
data = data[height_list, :, :] #remove 4th col too if rgba
data = data[:, width_list, :]
data2d = np.reshape(data,(len(height_list)*len(width_list), 3))
return data, data2d, scaling_param
def scatterplot_cs(data2d, colorspace, labels=None):
fig = plt.figure(figsize=(12,9))
ax = fig.gca(projection='3d')
x = data2d[:,0]
y = data2d[:,1]
z = data2d[:,2]
if labels is not None:
ax.scatter(x, y, z, c=labels.astype(float), edgecolor="k")
else:
ax.scatter(x, y, z, edgecolor="k")
if colorspace == "RGB":
ax.set_xlabel('Red Value')
ax.set_ylabel('Green Value')
ax.set_zlabel('Blue Value')
elif colorspace == "HSV":
ax.set_xlabel('Hue')
ax.set_ylabel('Saturation')
ax.set_zlabel('Value')
elif colorspace == "YCbCr":
ax.set_xlabel('Y - Luma Comp.')
ax.set_ylabel('Cb - Blue Diff.')
ax.set_zlabel('Cr - Red Diff.')
plt.show()
filename = "london-majus1.jpg"
data, data2d, scaling_param = reduce_image_res(filename, "RGB")
scatterplot_cs(data2d, "RGB", labels=None)
data, data2d, scaling_param = reduce_image_res(filename, "HSV")
scatterplot_cs(data2d, "HSV", labels=None)
data, data2d, scaling_param = reduce_image_res(filename, "YCbCr")
scatterplot_cs(data2d, "YCbCr", labels=None)
def agglomerative(data2d, n):
"""Only to use with small images, 200x100 pixel max!"""
est = AgglomerativeClustering(n_clusters=n, linkage="complete")
est.fit(data2d)
labels = est.labels_
#calculate centroids ourselves
labels_resh = np.array(labels).reshape((len(labels), 1))
data2d = np.append(data2d, labels_resh, axis=1)
n_points = [0 for i in range(n)]
y_sum = [0 for i in range(n)]
x_sum = [0 for i in range(n)]
z_sum = [0 for i in range(n)]
for point_i in range(len(data2d)):
point = data2d[point_i]
n_points[point[3]] += 1
y_sum[point[3]] += point[0]
x_sum[point[3]] += point[1]
z_sum[point[3]] += point[2]
centers = []
for cluster in range(n):
center = [y_sum[cluster]/n_points[cluster], x_sum[cluster]/n_points[cluster], z_sum[cluster]/n_points[cluster]]
centers.append(center)
centers = np.asarray(centers)
data2d = data2d[:, 0:3]
return centers, labels
Sorting functions from: https://www.alanzucconi.com/2015/09/30/colour-sorting/
def sort_colors(centers, colorspace, method):
if colorspace == "RGB":
centers_rgb = list(centers)
elif colorspace == "HSV":
#centers to rgb
centers_rgb = []
for color in centers:
hsv_string = f"hsv({color[0]},{int(color[1]/255*100)}%,{int(color[2]/255*100)}%)"
rgb = ImageColor.getrgb(hsv_string)
centers_rgb.append(rgb)
elif colorspace == "YCbCr":
def ycbcr2rgb(color):
Y, Cb, Cr = color
r = int(Y + 1.40200 * (Cr - 0x80))
g = int(Y - 0.34414 * (Cb - 0x80) - 0.71414 * (Cr - 0x80))
b = int(Y + 1.77200 * (Cb - 0x80))
r = max(0, min(255, r))
g = max(0, min(255, g))
b = max(0, min(255, b))
return [r, g, b]
centers_rgb = []
for color in centers:
rgb = ycbcr2rgb(color)
centers_rgb.append(rgb)
if method == "luminosity":
def lum (r,g,b):
return math.sqrt( .241 * r + .691 * g + .068 * b )
centers_rgb.sort(key=lambda rgb: lum(*rgb) )
centers_sorted = centers_rgb
elif method == "step_sort":
def step(r,g,b, repetitions=1):
lum = math.pow((0.299 * r + 0.587 * g + 0.114 * b), 1/2.2) #gamma corrected formula
h, s, v = colorsys.rgb_to_hsv(r,g,b)
h2 = int(h * repetitions)
#lum2 = int(lum * repetitions)
v2 = int(v * repetitions)
if h2 % 2 == 1:
v2 = repetitions - v2
lum = repetitions - lum
return (h2, lum, v2)
centers_rgb.sort(key=lambda rgb: step(*rgb,8))
centers_sorted = centers_rgb
print(centers_sorted)
elif method == "step_sort0":
def step (r,g,b, repetitions=1):
lum = math.sqrt( .241 * r + .691 * g + .068 * b )
h, s, v = colorsys.rgb_to_hsv(r,g,b)
h2 = int(h * repetitions)
lum2 = int(lum * repetitions)
v2 = int(v * repetitions)
return (h2, lum, v2)
centers_rgb.sort(key=lambda rgb: step(*rgb,8))
centers_sorted = centers_rgb
print(centers_sorted)
#to manually sort, as method we can put a list with the center indices in order
elif type(method) == list and len(method) == len(centers_rgb):
centers_sorted = centers_rgb[method]
elif method == "":
centers_sorted = centers_rgb
else:
print("Non-existant method")
centers_sorted = np.asarray(centers_sorted)
return centers_sorted
color = [12,59,177]
hsv_string = f"hsv({color[0]},{int(color[1]/255*100)}%,{int(color[2]/255*100)}%)"
print(hsv_string)
rgb = ImageColor.getrgb(hsv_string)
rgb
def image_from_centers(filename, data, centers_sorted):
centers_sorted = np.asarray(centers_sorted).astype(int)
n = len(centers_sorted)
#data to rgb
data_rgb = np.asarray(Image.open(filename))
#create new array with personalized sizes, default color value white?
mc_ratio = 9 #large in colorpalette cinema
height = data_rgb.shape[0]
width = data_rgb.shape[1]
margin = width/(n*mc_ratio+n+1)
palette = np.full((int(margin*(mc_ratio+2)), width, 3), 255, dtype='uint8')
#calculate regions to fill with color, fill them with sorted centers
for color in range(n):
curr_col = centers_sorted[color]
for h in range(int(margin), int(margin*(mc_ratio+1)), 1):
for w in range(int((color+1)*margin*(mc_ratio+1) - margin*mc_ratio), int((color+1)*margin*(mc_ratio+1)), 1):
palette[h][w] = curr_col
#attach to data array
full_arr = np.append(data_rgb, palette, axis=0)
image_new = Image.fromarray(full_arr)
display(image_new)
def palette_plots(filename, n, colorspace, method):
"""Master function"""
data, data2d = image_to_numpy(filename, colorspace)
print(f"Number of pixels in original image: {data2d.shape[0]}")
data_red, data2d_red, scaling_param = reduce_image_res(filename, colorspace)
print(f"Take every {scaling_param}-th pixel")
print(f"Number of data points in reduced image: {data2d_red.shape[0]}")
centers, labels = agglomerative(data2d_red, n)
scatterplot_cs(data2d_red, colorspace, labels)
print("Cluster centers:")
scatterplot_cs(centers, colorspace)
centers = sort_colors(centers, colorspace, method)
image_from_centers(filename, data, centers)
return data, centers
def palette_only(filename, n, clustering, method=""):
"""Master function"""
data, data2d = image_to_numpy(filename)
data_red, data2d_red, scaling_param = reduce_image_res(filename)
centers, labels = agglomerative(data2d_red, n, 'complete')
if method != "":
centers = sort_colors(centers, method=method)
image_from_centers(data, centers)
return data, centers
filename = "london-majus1.jpg"
data, centers = palette_plots(filename, 10, "HSV", method="dark_to_light")
data, centers = palette_plots(filename, 10, "RGB", method="dark_to_light")
data, centers = palette_plots(filename, 10, "YCbCr", method="luminosity")
data, centers = palette_plots('space_odyssey.jpg', 10, "HSV", method="luminosity")
data, centers = palette_plots('space_odyssey.jpg', 10, "RGB", method="luminosity")
data, centers = palette_plots('space_odyssey.jpg', 10, "YCbCr", method="step_sort")
data, centers = palette_plots('BladeRunner-Neon-1024x544.jpg', 10, "RGB", method="luminosity")
data, centers = palette_plots('BladeRunner-Neon-1024x544.jpg', 10, "YCbCr", method="luminosity")